(ns status-im.subs.wallet.swap
  (:require [clojure.string :as string]
            [re-frame.core :as rf]
            [status-im.constants :as constants]
            [status-im.contexts.wallet.common.utils :as utils]
            [status-im.contexts.wallet.send.utils :as send-utils]
            [status-im.contexts.wallet.swap.utils :as swap-utils]
            [utils.money :as money]
            [utils.number :as number]))

(rf/reg-sub
 :wallet/swap
 :<- [:wallet/ui]
 :-> :swap)

(rf/reg-sub
 :wallet/swap-asset-to-pay
 :<- [:wallet/swap]
 :-> :asset-to-pay)

(rf/reg-sub
 :wallet/swap-asset-to-pay-decimals
 :<- [:wallet/swap-asset-to-pay]
 :-> :decimals)

(rf/reg-sub
 :wallet/swap-asset-to-pay-symbol
 :<- [:wallet/swap-asset-to-pay]
 :-> :symbol)

(rf/reg-sub
 :wallet/swap-asset-to-receive
 :<- [:wallet/swap]
 :-> :asset-to-receive)

(rf/reg-sub
 :wallet/swap-network
 :<- [:wallet/swap]
 :-> :network)

(rf/reg-sub
 :wallet/swap-network-native-token-symbol
 :<- [:wallet/swap-network]
 :-> :native-currency-symbol)

(rf/reg-sub
 :wallet/swap-start-point
 :<- [:wallet/swap]
 :-> :start-point)

(rf/reg-sub
 :wallet/swap-error-response
 :<- [:wallet/swap]
 :-> :error-response)

(rf/reg-sub
 :wallet/swap-error-response-code
 :<- [:wallet/swap-error-response]
 :-> :code)

(rf/reg-sub
 :wallet/swap-error-response-details
 :<- [:wallet/swap-error-response]
 :-> :details)

(rf/reg-sub
 :wallet/swap-asset-to-pay-token-symbol
 :<- [:wallet/swap-asset-to-pay]
 :-> :symbol)

(rf/reg-sub
 :wallet/swap-updated-token-prices-usd
 :<- [:wallet/swap]
 :-> :updated-token-prices)

(rf/reg-sub
 :wallet/swap-asset-to-pay-network-balance
 :<- [:wallet/swap-asset-to-pay]
 :<- [:profile/currency]
 :<- [:profile/currency-symbol]
 :<- [:wallet/swap-asset-to-pay-token-symbol]
 :<- [:wallet/prices-per-token]
 (fn [[token currency currency-symbol token-symbol prices-per-token] [_ chain-id]]
   (let [{:keys [balances-per-chain
                 decimals]} token
         balance-for-chain  (get balances-per-chain chain-id)
         total-balance      (money/token->unit (:raw-balance balance-for-chain) decimals)
         fiat-value         (utils/calculate-token-fiat-value
                             {:currency         currency
                              :balance          total-balance
                              :token            token
                              :prices-per-token prices-per-token})
         crypto-formatted   (utils/get-standard-crypto-format token total-balance prices-per-token)
         fiat-formatted     (utils/fiat-formatted-for-ui currency-symbol fiat-value)]
     {:crypto (str crypto-formatted " " token-symbol)
      :fiat   fiat-formatted})))

(rf/reg-sub
 :wallet/swap-max-slippage
 :<- [:wallet/swap]
 :-> :max-slippage)

(rf/reg-sub
 :wallet/swap-approval-transaction-id
 :<- [:wallet/swap]
 :-> :approval-transaction-id)

(rf/reg-sub
 :wallet/swap-approval-transaction-status
 :<- [:wallet/transactions]
 :<- [:wallet/swap-approval-transaction-id]
 (fn [[transactions approval-transaction-id]]
   (get-in transactions [approval-transaction-id :status])))

(rf/reg-sub
 :wallet/swap-proposal
 :<- [:wallet/swap]
 :-> :swap-proposal)

(rf/reg-sub
 :wallet/swap-proposal-without-fees
 :<- [:wallet/swap]
 (fn [swap]
   (let [swap-proposal (:swap-proposal swap)]
     (reduce dissoc
             swap-proposal
             [:gas-fees :gas-amount :token-fees :bonder-fees :approval-fee :approval-l-1-fee]))))

(rf/reg-sub
 :wallet/swap-amount
 :<- [:wallet/swap]
 :-> :amount)

(rf/reg-sub
 :wallet/swap-approved-amount
 :<- [:wallet/swap]
 :-> :approved-amount)

(rf/reg-sub
 :wallet/swap-loading-swap-proposal?
 :<- [:wallet/swap]
 :-> :loading-swap-proposal?)

(rf/reg-sub
 :wallet/swap-loading-swap-proposal-fee?
 :<- [:wallet/swap]
 :-> :loading-swap-proposal-fee?)

(rf/reg-sub
 :wallet/swap-proposal-amount-out
 :<- [:wallet/swap-proposal]
 :-> :amount-out)

(rf/reg-sub
 :wallet/swap-proposal-amount-in
 :<- [:wallet/swap-proposal]
 :-> :amount-in)

(rf/reg-sub
 :wallet/swap-receive-amount
 :<- [:wallet/swap-proposal-amount-out]
 :<- [:wallet/swap-asset-to-receive]
 (fn [[amount-out asset-to-receive]]
   (let [receive-token-decimals (:decimals asset-to-receive)
         amount-out-whole       (when amount-out
                                  (number/hex->whole amount-out receive-token-decimals))
         amount-out-num         (when amount-out-whole
                                  (number/to-fixed amount-out-whole
                                                   receive-token-decimals))
         display-decimals       (min receive-token-decimals
                                     constants/min-token-decimals-to-display)
         receive-amount         (when amount-out-num
                                  (utils/sanitized-token-amount-to-display amount-out-num
                                                                           display-decimals))]
     (or receive-amount 0))))

(rf/reg-sub
 :wallet/swap-receive-amount-raw
 :<- [:wallet/swap-proposal-amount-out]
 :<- [:wallet/swap-asset-to-receive]
 (fn [[amount-out asset-to-receive]]
   (let [receive-token-decimals (:decimals asset-to-receive)
         amount-out-whole       (when amount-out
                                  (number/hex->whole amount-out receive-token-decimals))
         amount-out-num         (when amount-out-whole
                                  (number/to-fixed amount-out-whole
                                                   receive-token-decimals))]
     (or amount-out-num 0))))

(rf/reg-sub
 :wallet/swap-pay-amount
 :<- [:wallet/swap-proposal-amount-in]
 :<- [:wallet/swap-asset-to-pay]
 (fn [[amount-in asset-to-pay]]
   (let [pay-token-decimals (:decimals asset-to-pay)
         amount-in-whole    (when amount-in
                              (number/hex->whole amount-in pay-token-decimals))
         amount-in-num      (when amount-in-whole
                              (number/to-fixed amount-in-whole
                                               pay-token-decimals))
         display-decimals   (min pay-token-decimals
                                 constants/min-token-decimals-to-display)
         pay-amount         (when amount-in-num
                              (utils/sanitized-token-amount-to-display amount-in-num
                                                                       display-decimals))]
     (or pay-amount 0))))

(rf/reg-sub
 :wallet/swap-pay-amount-raw
 :<- [:wallet/swap-proposal-amount-in]
 :<- [:wallet/swap-asset-to-pay]
 (fn [[amount-in asset-to-pay]]
   (let [pay-token-decimals (:decimals asset-to-pay)
         amount-in-whole    (when amount-in
                              (number/hex->whole amount-in pay-token-decimals))
         amount-in-num      (when amount-in-whole
                              (number/to-fixed amount-in-whole
                                               pay-token-decimals))]
     (or amount-in-num 0))))

(rf/reg-sub
 :wallet/swap-proposal-provider
 :<- [:wallet/swap-proposal]
 (fn [swap-proposal]
   (when swap-proposal
     (let [bridge-name  (:bridge-name swap-proposal)
           provider-key (keyword (string/lower-case bridge-name))]
       (get constants/swap-providers provider-key)))))

(rf/reg-sub
 :wallet/swap-proposal-approval-required
 :<- [:wallet/swap-proposal]
 :-> :approval-required)

(rf/reg-sub
 :wallet/swap-proposal-approval-contract-address
 :<- [:wallet/swap-proposal]
 :-> :approval-contract-address)

(rf/reg-sub
 :wallet/swap-proposal-approval-amount-required
 :<- [:wallet/swap-proposal]
 :-> :approval-amount-required)

(rf/reg-sub
 :wallet/wallet-swap-proposal-fee-fiat
 :<- [:wallet/current-viewing-account]
 :<- [:wallet/swap-proposal]
 :<- [:profile/currency]
 :<- [:wallet/prices-per-token]
 :<- [:wallet/swap-network-native-token-symbol]
 (fn [[account swap-proposal currency prices-per-token native-token-symbol]]
   (when native-token-symbol
     (let [tokens              (:tokens account)
           token-for-fees      (first (filter #(= (string/lower-case (:symbol %))
                                                  (string/lower-case native-token-symbol))
                                              tokens))
           fee-in-native-token (send-utils/full-route-gas-fee [swap-proposal])
           fee-in-fiat         (utils/calculate-token-fiat-value
                                {:currency         currency
                                 :balance          fee-in-native-token
                                 :token            token-for-fees
                                 :prices-per-token prices-per-token})]
       fee-in-fiat))))

(rf/reg-sub
 :wallet/swap-asset-to-pay-balance-for-chain-data
 :<- [:wallet/swap-asset-to-pay]
 :<- [:wallet/swap-network-native-token-symbol]
 (fn [[asset-to-pay native-token-symbol]]
   (when-let [token-symbol (or (:symbol asset-to-pay) native-token-symbol)]
     @(rf/subscribe [:wallet/token-by-symbol token-symbol]))))

(rf/reg-sub
 :wallet/swap-asset-to-pay-balance-for-chain
 :<- [:wallet/swap-asset-to-pay-balance-for-chain-data]
 (fn [asset-to-pay-with-current-account-balance [_ chain-id]]
   (let [pay-token-decimals               (:decimals asset-to-pay-with-current-account-balance)
         pay-token-balance-selected-chain (-> (get-in asset-to-pay-with-current-account-balance
                                                      [:balances-per-chain chain-id :raw-balance]
                                                      0)
                                              (number/convert-to-whole-number pay-token-decimals))]
     pay-token-balance-selected-chain)))

(rf/reg-sub
 :wallet/swap-asset-to-pay-amount-in-fiat
 :<- [:wallet/swap-asset-to-pay-balance-for-chain-data]
 :<- [:wallet/prices-per-token]
 :<- [:profile/currency]
 :<- [:profile/currency-symbol]
 (fn [[asset-to-pay-with-current-account-balance prices-per-token currency currency-symbol] [_ amount]]
   (utils/formatted-token-fiat-value
    {:currency         currency
     :currency-symbol  currency-symbol
     :balance          (or amount 0)
     :token            asset-to-pay-with-current-account-balance
     :prices-per-token prices-per-token})))

(rf/reg-sub
 :wallet/swap-approval-fee
 :<- [:wallet/swap-proposal]
 (fn [{:keys [approval-fee approval-l-1-fee]}]
   (money/add (money/wei->ether approval-fee)
              (money/wei->ether approval-l-1-fee))))

(rf/reg-sub
 :wallet/approval-gas-fees
 :<- [:wallet/current-viewing-account]
 :<- [:wallet/swap-approval-fee]
 :<- [:profile/currency]
 :<- [:wallet/prices-per-token]
 :<- [:wallet/swap-network-native-token-symbol]
 (fn [[account approval-fee currency prices-per-token native-token-symbol]]
   (when native-token-symbol
     (let [tokens         (:tokens account)
           token-for-fees (first (filter #(= (string/lower-case (:symbol %))
                                             (string/lower-case native-token-symbol))
                                         tokens))
           fee-in-fiat    (utils/calculate-token-fiat-value
                           {:currency         currency
                            :balance          approval-fee
                            :token            token-for-fees
                            :prices-per-token prices-per-token})]
       fee-in-fiat))))

;; NOTE: updated route prices come only in USD (for now). When the router will
;; allow to define the currency, we should use only the updated prices, but till
;; then we fallback to `prices-per-token`
(rf/reg-sub :wallet/swap-pay-token-price
 :<- [:wallet/swap-asset-to-pay]
 :<- [:profile/currency]
 :<- [:wallet/swap-updated-token-prices-usd]
 :<- [:wallet/prices-per-token]
 (fn [[asset-to-pay currency updated-token-prices prices-per-token]]
   (if (= currency constants/profile-default-currency)
     (swap-utils/updated-token-price asset-to-pay updated-token-prices)
     (utils/token-price-by-symbol prices-per-token (:symbol asset-to-pay) currency))))

(rf/reg-sub :wallet/swap-receive-token-price
 :<- [:wallet/swap-asset-to-receive]
 :<- [:profile/currency]
 :<- [:wallet/swap-updated-token-prices-usd]
 :<- [:wallet/prices-per-token]
 (fn [[asset-to-receive currency updated-token-prices prices-per-token]]
   (if (= currency constants/profile-default-currency)
     (swap-utils/updated-token-price asset-to-receive updated-token-prices)
     (utils/token-price-by-symbol prices-per-token (:symbol asset-to-receive) currency))))

(rf/reg-sub
 :wallet/swap-exchange-rate-crypto
 :<- [:wallet/swap-pay-token-price]
 :<- [:wallet/swap-receive-token-price]
 :<- [:wallet/swap-asset-to-pay]
 :<- [:wallet/swap-updated-token-prices-usd]
 (fn [[pay-token-price receive-token-price asset-to-pay token-prices]]
   (when (and token-prices asset-to-pay)
     (let [exchange-rate (some->> asset-to-pay
                                  :decimals
                                  (utils/token-exchange-rate pay-token-price receive-token-price))]
       (or exchange-rate 0)))))

(rf/reg-sub
 :wallet/swap-exchange-rate-fiat
 :<- [:wallet/swap-pay-token-price]
 :<- [:profile/currency-symbol]
 (fn [[pay-token-price currency-symbol]]
   (when pay-token-price
     (utils/fiat-formatted-for-ui currency-symbol pay-token-price))))

(rf/reg-sub :wallet/max-swap-fee
 :<- [:wallet/swap-proposal-approval-required]
 :<- [:wallet/swap-approval-fee]
 :<- [:wallet/swap-proposal]
 (fn [[approval-required swap-approval-fee swap-proposal]]
   (let [wallet-swap-proposal-fee (send-utils/full-route-gas-fee [swap-proposal])]
     (if approval-required
       (money/add swap-approval-fee wallet-swap-proposal-fee)
       wallet-swap-proposal-fee))))

(rf/reg-sub :wallet/swap-available-crypto-limit
 :<- [:wallet/swap-network]
 :<- [:wallet/swap-asset-to-pay-balance-for-chain-data]
 :<- [:wallet/max-swap-fee]
 :<- [:wallet/swap-asset-to-pay-symbol]
 :<- [:wallet/swap-network-native-token-symbol]
 (fn [[network asset-to-pay-with-current-account-balance max-swap-fee pay-token-symbol
       native-token-symbol]]
   (let [pay-token-balance (utils/token-balance-for-network
                            asset-to-pay-with-current-account-balance
                            (:chain-id network))]
     (if (= pay-token-symbol native-token-symbol)
       (let [buffered-fee (money/mul max-swap-fee
                                     (inc (/ constants/eth-max-fee-buffer-percent 100)))]
         (money/sub pay-token-balance buffered-fee))
       pay-token-balance))))

(rf/reg-sub
 :wallet/swap-estimated-time
 :<- [:wallet/swap-proposal]
 (fn [route]
   (-> route
       :estimated-time
       utils/estimated-time-v2-format)))

(rf/reg-sub
 :wallet/swap-approval-estimated-time
 :<- [:wallet/swap-proposal]
 (fn [route]
   (some-> route
           :approval-estimated-time
           utils/estimated-time-v2-format)))
